#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##--------------------------------------------------------------------------##

from collections import namedtuple
import logging

Logger = logging.getLogger('PySolFC.Layout')

canvasSize = namedtuple('CanvasSize', ('width', 'height'))

from .text import TextLayout
from .card import CardLayout
from .group import RegionLayout

from ..packlib import info as CSI
from ..utillib.misc import kwdefault
from ..settings import CONF_OPT

LEFT, RIGHT, BELOW, ABOVE = 1, -1, 1, -1


class Layout(TextLayout, CardLayout, RegionLayout, object):

    def __init__(self, game, **kw):
        """
        __init__(game:obj) -> None
        """
        self.game = game
        self.canvas = game.canvas
        self.texts = game.texts
        self.size = None
        self.s = dict(talon=None, wastes=[], foundations=[], rows=[],
                        reserves=[], braid=[], internals=[])
        self.stackmap = {}
        self._regions = []
        # set visual constants
        images = game.images
        self.CW, self.CH = images.size
        self.HCW, self.HCH = self.CW / 2, self.CH / 2
        self.XOFFSET, self.YOFFSET = images.offset
        self.XM = self.YM = images.space.xspace - images.size.width
        self.XS, self.YS = images.space
        self.HXS, self.HYS = self.XS / 2, self.YS / 2
        self.NDECKS = game['decks']
        self.TRUMPS = game['trumps']
        self.SUIT = game['suits']
        if self.TRUMPS and kw.get('trump', False):
            suits = list(self.SUIT)
            suits.append(self.SUIT[-1] + 1)
            self.SUIT = suits
        self.SUITS = self.SUIT * self.NDECKS
        self.RANK = game['ranks']
        self.RANKS = self.RANK * self.NDECKS
        self.NSUIT = len(self.SUIT)
        self.NSUITS = len(self.SUITS)
        self.NRANKS = len(self.RANKS)
        self.NCARDS = game['ncards']
        self.TEXT_MARGIN = int(CONF_OPT['fonts']['canvas_default'][1])
        self.TEXT_HEIGHT = 18 + self.TEXT_MARGIN
        self._has_waste = (hasattr(game, '_waste_') and
                           game._waste_ is not None)
        self.size = canvasSize(0, 0,)

    def defaultAll(self):
        """
        defaultAll() -> None

        Set default for texts, groups, and regions.
        """
        game = self.game
        # create texts
        talon = self.s['talon']
        if game.s['talon'] and talon:
            if talon.ncards:
                self.set_ncards_text(game.s['talon'], **talon.ncards)
            if talon.rounds:
                self.set_rounds_text(game.s['talon'], **talon.rounds)
        for key in ['wastes', 'reserves', 'foundations']:
            gpiles, lpiles = game.s[key], self.s[key]
            for gpile, lpile in zip(gpiles, lpiles):
                if lpile.ncards:
                    self.set_ncards_text(gpile, **lpile.ncards)
        self.defaultStackGroups()
        self._apply_group_regions(game)

    def defaultStackGroups(self):
        """
        defaultStackGroups() -> None

        Set default for groups.
        """
        sg, s = self.game.sg, self.game.s
        sg['Talons'] = [s['talon']] + s['wastes']
        sg['dropstacks'] = s['rows'] + s['reserves'] + s['wastes']
        sg['openstacks'] = s['foundations'] + s['rows'] + s['reserves']
        sg['reservestacks'] = s['reserves']
        if hasattr(self.game, '_xwaste_') and self.game._xwaste_ is not None:
            sg['reservestacks'] += s['wastes'][:-1]

class CongressLayout(Layout):
    """
    static block (left):
        - talon (corner).
        - waste (if Game._waste_. wastecards ignored).
        - rows / 2 (vert. playcards ignored)
        - foundations (vert SUITS. max of NSUIT rows)
        - rows / 2 (vert. playcards ignored)
    """

    def create(self, rows=8, texts=True, **kw):
        l, s = self, self.s
        assert l.size.width == 0
        l._set_horz_talon(x=l.XM, y=l.YM, texts=texts, align=LEFT,
                          anchor='s', wastecards=0)
        rows = l._get_row_size(rows, 2)
        s['talon'].set_rounds(anchor='sss' if l._has_waste else 'e')
        dx = l.size.width + l.XM + ((2 + l.NDECKS) * l.XS / l.NDECKS)
        l._set_vert_foundations(x=dx - l.NDECKS * l.HXS, y=l.YM,
                                nrows=l.NSUIT, bases=l.SUITS)
        for y, row in zip(l.row_iter(l.YM, len(rows)), rows):
            for x in l.col_iter(dx - (l.NDECKS / 2 + 1) * l.XS, row,
                                dx=(l.NDECKS + 1) * l.XS):
                s['rows'].append(l._create_stack(x, y))
                l.s['rows'][-1].set_offset(0, 0)


class TowersLayout(Layout):
    """
    static block (top):
        - reserves. (horz. holdcards ignored. Ncards if reserve_texts.)
          foundations. (horz SUIT * 2. max of 10 cols.)
        - rows. (_offset if playcards.)

    dynamic block (bottom): align LEFT or RIGHT side. default LEFT.
        - talon (corner). waste (if Game._waste_. wastecards ignored).

    :cvar func _offset: dict('ycards': value)
    """

    _offset = lambda self, cards: {'ycards': cards}
    _halign = lambda self, max_rows, piles: (max_rows - piles) * self.HXS

    def create(self, rows, ncols=None, playcards=18, bases=None,
               reserves=0, reserve_texts=False, texts=False, wastecards=0,
               align=LEFT, **kw):
        l, s = self, self.s
        assert l.size.width == 0
        ncols = ncols or rows
        # set foundation base caps
        bases = bases if type(bases) in (list, tuple) else l.SUITS
        max_cols = max(ncols, reserves + 4)
        center = lambda piles: l._halign(max_cols, piles)
        l._set_horz_reserves(x=l.XM + center(reserves), y=l.YM,
                             reserves=reserves, reserve_texts=reserve_texts,
                             anchor='s')
        dy = l.TEXT_HEIGHT if reserves and reserve_texts else 0
        l._set_rows(x=l.XM + center(ncols), y=l.size.height + dy, rows=rows,
                    ncols=ncols, **l._offset(playcards))
        l._set_horz_foundations(x=l.XM, y=l.YM, ncols=2, bases=bases[::2])
        l._set_horz_foundations(x=l.XM + l.size.width - 2 * l.XS, y=l.YM,
                                ncols=2, bases=bases[1::2])
        dx = l.XM if align == LEFT else l.size.width - l.XS
        dy = l.TEXT_HEIGHT if texts else 0
        l._set_horz_talon(x=dx, y=l.size.height + dy, texts=texts,
                          align=align, anchor='n', wastecards=wastecards)
        anchor = 'w' if align== RIGHT else'e'
        s['talon'].set_rounds(anchor='nnn' if l._has_waste and wastecards
                                        else anchor,
                           dx=l.XS * align if l._has_waste and not wastecards
                                           else 0)


class GloriaLayout(Layout):
    """
    static block (top):
        - reserves. (horz. holdcards ignored. Ncards if reserve_texts.)
          foundations. (horz SUIT * 2. max of 10 cols.)
        - rows. (_offset if playcards.)

    dynamic block (bottom): align LEFT or RIGHT side. default LEFT.
        - talon (corner). waste (if Game._waste_. wastecards ignored).

    :cvar func _offset: dict('ycards': value)
    """

    _offset = lambda self, cards: {'ycards': cards}
    _halign = lambda self, max_rows, piles: (max_rows - piles) * self.HXS

    def create(self, rows, ncols=None, playcards=18, bases=None,
               reserves=0, reserve_texts=False, texts=False, wastecards=0,
               align=LEFT, **kw):
        l, s = self, self.s
        assert l.size.width == 0
        ncols = ncols or rows
        # set foundation base caps
        bases = bases if type(bases) in (list, tuple) else l.SUITS
        f_cols = min(5, len(bases) / l.NDECKS)
        max_cols = max(ncols, reserves + f_cols)
        center = lambda piles: l._halign(max_cols, piles)
        l._set_horz_foundations(x=l.XM + center(f_cols), y=l.YM, ncols=f_cols,
                                bases=bases)
        dy = l.TEXT_HEIGHT if reserves and reserve_texts else 0
        l._set_rows(x=l.XM + center(ncols), y=l.size.height + dy, rows=rows,
                    ncols=ncols, **l._offset(playcards))
        l._set_horz_reserves(x=l.XM, y=l.YM, reserves=1,
                             reserve_texts=reserve_texts, anchor='s')
        l._set_horz_reserves(x=l.XM + l.size.width - l.XS, y=l.YM, reserves=1,
                             reserve_texts=reserve_texts, anchor='s')
        dx = l.XM if align == LEFT else l.size.width - l.XS
        dy = l.TEXT_HEIGHT if texts else 0
        l._set_horz_talon(x=dx, y=l.size.height + dy, texts=texts,
                          align=align, anchor='n', wastecards=wastecards)
        anchor = 'w' if align== RIGHT else'e'
        s['talon'].set_rounds(anchor='nnn' if l._has_waste and wastecards
                                        else anchor,
                           dx=l.XS * align if l._has_waste and not wastecards
                                           else 0)


class LeprechaunLayout(Layout):
    """
    - top: foundations
    - below: (reserves)
    - below: rows
    - left bottom: talon
    """

    def create(self, rows, ncols=0, playcards=18, bases=None,
               reserves=0, reserve_texts=False, texts=True, **kw):
        l, s = self, self.s
        ncols = ncols or rows
        bases = bases if type(bases) in (list, tuple) else l.SUITS
        f_size = min(10, (len(bases) / l.NDECKS) * min(2, l.NDECKS))
        l.set_vert_talon(x=l.XM, y=l.YM, texts=texts, align=LEFT,
                         anchor='e', wastecards=0)
        s['talon'].set_rounds(anchor='se')
        dx = l.size.width + l.XM
        dy = l.TEXT_HEIGHT if reserve_texts else 0
        l._set_horz_reserves(x=dx, y=l.YM + dy, reserves=reserves, anchor='n',
                             reserve_texts=reserve_texts)
        l._set_horz_foundations(x=dx, y=l.size.height, size=f_size,
                                bases=bases)
        l._set_rows(x=dx, y=l.size.height, rows=rows, ncols=ncols,
                    ycards=playcards)


class CurdsWheyLayout(Layout):
    """
    - top: rows. Vert offset when playcards
    - bottom: waste, talon. Waste with horz offset when wastecards
    """

    def create(self, rows, reserves=0, reserve_texts=False, ncols=None,
               texts=True, playcards=18, align=RIGHT, wastecards=0, **kw):
        l, s = self, self.s
        l._set_rows(x=l.XM, y=l.YM, rows=rows, ncols=ncols or rows,
                    ycards=playcards)
        dy = l.TEXT_HEIGHT if reserve_texts or texts else 0
        y = l.size.height + dy
        x = l.XM if align == RIGHT else l.size.width - reserves * l.XS
        l._set_horz_reserves(x=x, y=y, reserves=reserves,
                             anchor='n', reserve_texts=reserve_texts)
        x = l.size.width - l.XS if align == RIGHT else l.XM
        l.set_horz_talon(x=x, y=y, texts=texts, align=align, anchor='n',
                         wastecards=wastecards)
        s['talon'].set_rounds(anchor='nnn' if l._has_waste
                                  else 'e' if align == LEFT else 'w')


class Trusty12Layout(Layout):
    """
    - top: rows. Vert offset when playcards
    - bottom: waste, talon.
    """

    def create(self, rows, reserves=0, ncols=None, texts=True, playcards=18,
              align=LEFT, reserve_texts=False, **kw):
        l, s = self, self.s
        dx = (l.XM if align == RIGHT
                   else l.XM + (1 + int(l._has_waste)) * l.XS)
        l._set_rows(x=dx, y=l.YM, rows=rows, ncols=ncols or rows,
                    ycards=playcards)
        dx = (l.size.width + l.XM if align == RIGHT else l.XM)
        l._set_horz_talon(x=dx, y=l.YM, texts=texts,
                         align=align, anchor='s',
                         wastecards=0)
        s['talon'].set_rounds(anchor='sss' if l._has_waste
                                 else 'se' if align == LEFT else 'sw')
        dy = l.TEXT_HEIGHT if reserve_texts else 0
        l._set_vert_reserves(x=l.XM, y=l.YM + l.YS + dy, reserves=reserves,
                             anchor='n', reserve_texts=reserve_texts)


class StrataLayout(Layout):
    """
    - top: foundations
    - below: rows. Vert offset when playcards
    - side: waste, talon. center vert
    """

    def create(self, rows, ncols=None, texts=True, playcards=18,
               align=LEFT, bases=None, **kw):
        l, s = self, self.s
        ncols = ncols or rows
        bases = bases if type(bases) in (list, tuple) else l.SUITS
        f_size = min(10, (len(bases) / l.NDECKS) * min(2, l.NDECKS))
        sx = (l.XM if align == RIGHT
                   else l.XM + (1 + int(l._has_waste)) * l.XS)
        l._set_horz_foundations(x=sx, y=l.size.height, size=f_size,
                                bases=bases)
        l._set_rows(x=sx, y=l.YM, rows=rows, ncols=ncols or rows,
                    ycards=playcards)
        dy = (len(bases) / f_size + int(len(bases) % f_size > 0)) * l.HYS
        l.set_horz_talon(y=dy, texts=texts,
                         align=align, anchor='n', wastecards=0)
        s['talon'].set_rounds(anchor='nnn' if l._has_waste
                                 else 'e' if align == LEFT else 'w')


class CatsTailLayout(Layout):
    """
    static block (top):
      - reserves. (holdcards ignored. ncards if reserve_texts).
      - rows. (_offset if playcards).
    dynamic block (bottom):  align LEFT or RIGHT side. default RIGHT.
      - foundations. (vert SUITS. max of 5 rows)
      - talon (corner). waste (if Game._waste_. wastecards ignored).

    :cvar func _offset: dict('ycards': value)
    """

    _offset = lambda self, cards: {'ycards': cards}

    def create(self, rows, ncols=2, playcards=9, bases=None,
               reserves=0, reserve_texts=False,
               align=RIGHT, texts=False, **kw):
        l, s = self, self.s
        bases = bases if type(bases) in (list, tuple) else l.SUITS
        max_rows = max(rows / ncols, reserves)
        f_rows = min(5, l.NSUIT if bases is l.SUITS else len(bases))
        sx = 0 if align == RIGHT else l.XM + bases / f_rows * l.XS
        center = lambda p: l.XM + sx + (max_rows - p) * l.HXS / 2
        l._set_horz_reserves(x=center(reserves), y=l.YM,
                             reserves=reserves, reserve_texts=reserve_texts,
                             anchor='n')
        dy = l.TEXT_HEIGHT if reserves and reserve_texts else 0
        l._set_rows(x=center(rows), y=l.size.height + dy, rows=rows,
                    ncols=ncols, **l._offset(playcards))

        dx = l.size.width if align == RIGHT else 0
        l._set_vert_foundations(x=l.XM + dx, y=l.YM, nrows=f_rows,
                                bases=bases)
        dx = l.XM if align == LEFT else l.size.width - l.XS
        dy = l.TEXT_HEIGHT if texts else 0
        l._set_horz_talon(x=dx, y=l.size.height + dy, anchor='n', texts=texts,
                          align=align, wastecards=0)
        anchor = 'w' if align== RIGHT else'e'
        s['talon'].set_rounds(anchor=anchor,
                           dx=l.XS * align if l._has_waste else 0)


class HarpLayout(Layout):
    """
    static block (top):
        - reserves. (_offset if holdcards. ncards if reserve_texts).
        - rows. (_offset if playcards).
        - foundations. (horz SUIT * 2. max of 10 cols)

    dynamic block (bottom):  align LEFT or RIGHT side. default RIGHT.
        - talon (corner). waste (if Game._waste_. wastecards ignored).

    :cvar func _offset: dict('ycards': value)
    """

    _offset = lambda self, cards: {'ycards': cards}

    def create(self, rows, ncols=None, playcards=12, bases=None,
               reserves=0, reserve_texts=False, holdcards=0,
               align=RIGHT, texts=False, wastecards=0):
        l, s = self, self.s
        ncols = ncols or rows
        bases = bases if type(bases) in (list, tuple) else l.SUITS
        f_cols = min(10, (len(bases) / l.NDECKS) * min(2, l.NDECKS))
        max_rows = max(rows, reserves, f_cols)
        center = lambda p: (max_rows - p) * l.HXS / 2
        dy = l.TEXT_HEIGHT if reserves and reserve_texts else 0
        l._set_horz_reserves(x=l.XM + center(reserves), y=l.size.height + dy,
                            reserves=reserves, reserve_texts=reserve_texts,
                            anchor='n', **l._offset(holdcards))
        l._set_rows(x=l.XM + center(rows), y=l.size.height, rows=rows,
                    ncols=ncols, **l._offset(playcards))
        sx = (0 if align == RIGHT
                else l.XM + (1 + int(l._has_waste)) * l.XS)
        dy = l.size.height
        l._set_horz_foundations(x=l.XM + sx, y=dy, ncols=f_cols, bases=bases)
        dx = 0 if align == LEFT else l.s['foundations'][-1].x + l.XS
        dy += (len(bases) / f_cols + int(len(bases) % f_cols > 0)) * l.HYS
        l._set_horz_talon(x=l.XM + dx, y=dy, texts=texts, align=align,
                          anchor='n', wastecards=wastecards)
        anchor = 'w' if align== RIGHT else'e'
        s['talon'].set_rounds(anchor='nnn' if l._has_waste else anchor)


class HarpAltLayout(Layout):
    """
    static block (top):
        - reserves. (_offset if holdcards. ncards if reserve_texts).
        - rows. (_offset if playcards).
        - foundations. (horz SUIT * 2. max of 10 cols)

    dynamic block (bottom):  align LEFT or RIGHT side. default RIGHT.
        - talon (corner). waste (if Game._waste_. wastecards ignored).

    :cvar func _offset: dict('xcards': value)
    """

    _offset = lambda self, cards: {'xcards': cards}


class KlondikeLayout(Layout):
    """
    static block (top):
        - foundations. (horz SUIT * 2. max of 10 cols)
        - rows. (_offset if playcards).
        - reserves. (_offset if holdcards. ncards if reserve_texts).

    dynamic block:  align LEFT or RIGHT side. default LEFT.
        - talon (top corner). waste (if Game._waste_. wastecards ignored).

    :cvar func _offset: dict('ycards': value)
    """

    _offset = lambda self, cards: {'ycards': cards}

    def create(self, rows, ncols=None, playcards=16, bases=None,
               reserves=0, holdcards=0, holdtexts=False,
               align=LEFT, texts=False, **kw):
        l, s = self, self.s
        ncols = ncols or rows
        bases = bases if type(bases) in (list, tuple) else l.SUITS
        f_cols = min(10, (len(bases) / l.NDECKS) * min(2, l.NDECKS))
        max_rows = max(rows, reserves, f_cols + 1 + int(l._has_waste))
        center = lambda p: (max_rows - p) * l.HXS
        ix = l.size.width + l.XM
        sx = (0 if align == RIGHT else l.XM + (1 + int(l._has_waste)) * l.XS)
        sx += center(1 + int(l._has_waste) + f_cols)
        l._set_horz_foundations(x=ix + sx, y=l.YM, ncols=f_cols, bases=bases)
        dy = l.TEXT_HEIGHT if texts else 0
        l._set_rows(x=ix + center(rows), y=l.size.height + dy, rows=rows,
                    ncols=ncols, **l._offset(playcards))
        dy = l.TEXT_HEIGHT if holdtexts else 0
        l._set_horz_reserves(x=ix + center(reserves), y=l.size.height + dy,
                            reserves=reserves, reserve_texts=holdtexts,
                            anchor='n', **l._offset(holdcards))
        dx = ix if align == LEFT else l.size.width - l.XS
        dy = (len(bases) / f_cols + int(len(bases) % f_cols > 0) - 1) * l.HYS
        l._set_horz_talon(x=dx + l.XM, y=l.YM + dy, texts=texts, align=align,
                          anchor='s', wastecards=0)
        anchor = 'w' if align== RIGHT else'e'
        s['talon'].set_rounds(anchor='ss' if l._has_waste else anchor)


class DukeLayout(Layout):
    """
    static block: (top):
        - foundations. (horz SUIT * 2. max of 10 cols)
        - rows. (_offset if playcards).

    dynamic block:  align LEFT or RIGHT side. default LEFT.
        - talon (top corner). waste (if Game._waste_. wastecards ignored).
        - reserves. (opposite talon side. _offset if holdcards. reserve_texts
                     ignored. ncols wide.).

    :cvar func _offset: dict('ycards': value)
    """

    _offset = lambda self, cards: {'ycards': cards}

    def create(self, rows, ncols=3, playcards=12, bases=None, reserves=0,
               holdcards=0, align=LEFT, texts=False, **kw):
        l, s = self, self.s
        ncols = ncols or reserves
        bases = bases if type(bases) in (list, tuple) else l.SUITS
        f_cols = min(10, (len(bases) / l.NDECKS) * min(2, l.NDECKS))
        t_size = 1 + int(l._has_waste)
        max_rows = max(rows, reserves + t_size)
        center = lambda p: (max_rows - p) * l.HXS / 2
        ix = l.size.width + l.XM
        sx = 0 if align == LEFT else l.XM + ncols * l.XS
        sx += 0 if align == RIGHT else l.XM + t_size * l.XS
        sx += center(t_size + f_cols)
        l._set_horz_foundations(x=ix + sx, y=l.YM,
                                ncols=f_cols, bases=bases)
        dy = l.TEXT_HEIGHT if texts else 0
        l._set_rows(x=ix + center(rows), y=l.size.height + dy, rows=rows,
                    ncols=ncols, **l._offset(playcards))
        dx = 0 if align == LEFT else l.size.width - l.XS
        dy = (len(bases) / f_cols + int(len(bases) % f_cols > 0) - 1) * l.HYS
        l._set_horz_talon(x=ix + dx, y=l.YM + dy, texts=texts, align=align,
                          anchor='s', wastecards=0)
        anchor = 'w' if align== RIGHT else'e'
        s['talon'].set_rounds(anchor='ss' if l._has_waste else anchor)
        dx = l.size.width if align == LEFT else 0
        l.set_reserves(x=dx, y=l.YM, reserves=reserves, ncols=ncols,
                       **l._offset(holdcards))


class PhoenixLayout(KlondikeLayout):
    """
    static block (top):
        - Talon (corner). Waste (if Game._waste_. wastecards ignored).
          foundations (horz suits. max of 10)
          reserves (holdcards ignored. reserve_texts ignored).
        - rows (l.YOFFSET if playcards).
    """

    def create(self, rows, ncols=3, playcards=12, bases=None,
               reserves=0, texts=False, **kw):
        l, s = self, self.s
        ix = l.size.width + l.XM
        reserves /= 2
        l.set_vert_reserves(x=ix, y=l.YM, reserves=reserves,
                            reserve_texts=False, anchor='e')
        super(PhoenixLayout, self).create(rows=rows, playcards=playcards,
                                              bases=bases, align=LEFT,
                                              texts=False)
        ix = l.size.width + l.XM
        l.set_vert_reserves(x=ix, y=l.YM, reserves=reserves,
                            reserve_texts=False, anchor='e')
        offset = (0, l.YM / 3)
        for r in s['reserves']:
            s['reserves'][-1].set_offset(*offset)


class JaneLayout(KlondikeLayout):
    """
    static block: (top):
        - foundations. (horz SUIT * 2. max of 10 cols)
        - rows. (_offset if playcards).

    dynamic block:  align LEFT or RIGHT side. default LEFT.
        - talon (top corner). waste (if Game._waste_. wastecards ignored).
        - reserves. (opposite talon side. _offset if holdcards. reserve_texts
                     ignored. stagger vert.).

    :cvar func _offset: dict('ycards': value)
    """

    _offset = lambda self, cards: {'ycards': cards}

    def create(self, rows, ncols=None, playcards=12, bases=None, reserves=0,
               texts=False, align=LEFT, **kw):
        l, s = self, self.s
        ncols = ncols or reserves
        bases = bases if type(bases) in (list, tuple) else l.SUITS
        f_cols = min(10, (len(bases) / l.NDECKS) * min(2, l.NDECKS))
        t_size = 1 + int(l._has_waste)
        max_rows = max(rows, reserves + t_size)
        center = lambda p: (max_rows - p) * l.HXS / 2
        ix = l.size.width + l.XM
        sx = 0 if align == LEFT else l.XM + ncols * l.XS
        sx += 0 if align == RIGHT else l.XM + t_size * l.XS
        sx += center(t_size + f_cols)
        l._set_horz_foundations(x=ix + sx, y=l.YM,
                                ncols=f_cols, bases=bases)
        dy = l.TEXT_HEIGHT if texts else 0
        l._set_rows(x=ix + center(rows), y=l.size.height + dy, rows=rows,
                    ncols=ncols, **l._offset(playcards))
        dx = 0 if align == LEFT else l.size.width - l.XS
        dy = (len(bases) / f_cols + int(len(bases) % f_cols > 0) - 1) * l.HYS
        l._set_horz_talon(x=ix + dx, y=l.YM + dy, texts=texts, align=align,
                          anchor='s', wastecards=0)
        anchor = 'w' if align== RIGHT else'e'
        s['talon'].set_rounds(anchor='ss' if l._has_waste else anchor)
        dx = l.size.width if align == LEFT else 0
        y_ = l.YM
        offset = (0, l.YM / 3)
        start = 1 if align == LEFT else 0
        for i in range(start, reserves + start):
            s['reserves'].append(l._create_stack(l.XM + dx + (i & 1) * l.XS, y_))
            s['reserves'][-1].set_offset(*offset)
            y_ += l.HYS
        self._regions += [(self.s['reserves'], self._region(
                                    x=l.size.width + l.XM, y=l.YM,
                                    w=dx + 2 * self.XS, h=y_ + self.YS),)]


class SenateLayout(Layout):
    """
    static block (top):
        - Talon (corner). Waste (if Game._waste_. wastecards ignored).
          foundations (horz suits. max of 10)
          reserves (holdcards ignored. reserve_texts ignored).
        - rows (l.YOFFSET if playcards).
    """

    def create(self, rows=4, reserves=8, ncols=4, playcards=12,
                     bases=None, holdcards=10, texts=True, **kw):
        l, s = self, self.s
        bases = bases if type(bases) in (list, tuple) else l.SUITS
        f_rows = min(5, l.NSUIT if bases is l.SUITS else len(bases))
        l.set_rows(x=l.XM, y=l.YM, rows=rows, ycards=playcards)
        l.set_reserves(x=l.size.width + l.XM, y=l.YM, reserves=reserves,
                       ncols=ncols)
        l._set_vert_foundations(x=l.size.width + l.XM, y=l.YM, ncols=f_rows,
                                bases=bases)
        dy = l.TEXT_HEIGHT if texts else 0
        l._set_horz_talon(x=l.size.width - l.XS, y=l.YM + dy, texts=texts,
                          align=RIGHT, anchor='n', wastecards=0)


class CicelyLayout(Layout):
    """
    static block (top):
        - foundations (horz suits. max of 10)
        - reserves (vert. l.YOFFSET if holdcards. Ncards if reserve_texts).
        - rows (l.YOFFSET if playcards).

    dynamic block (top): align LEFT or RIGHT side. default LEFT.
        above:Talon (corner). Waste (if Game._waste_. wastecards ignored).

    :cvar func _offset: dict('xcards': value)
    """

    _offset = lambda self, cards: {'xcards': cards}

    def create(self, rows, ncols=None, playcards=9, bases=None,
               reserves=0, holdcards=0, reserve_texts=False,
               texts=False, align=LEFT):
        l, s = self, self.s
        ncols = ncols or rows
        bases = bases if type(bases) in (list, tuple) else l.SUITS
        f_cols = min(10, (len(bases) / l.NDECKS) * min(2, l.NDECKS))
        max_rows = max(1 + int(self._has_waste) + f_cols, reserves, rows)
        center = lambda p: (max_rows - p) * l.HXS
        ix = l.size.width + l.XM
        sx = (0 if align == RIGHT
                else l.XM + (1 + int(l._has_waste)) * l.XS)
        l._set_horz_foundations(x=ix + sx, y=l.YM, ncols=f_cols,
                                bases=bases)
        dy = l.size.height + (l.TEXT_HEIGHT if texts else 0)
        l._set_horz_reserves(x=ix + sx+ center(reserves), y=dy,
                             reserves=reserves, reserve_texts=reserve_texts,
                             anchor='n', **l._offset(holdcards))
        dy = l.size.height + (l.TEXT_HEIGHT if reserve_texts else 0)
        l._set_rows(x=ix + sx +center(rows), y=dy, rows=rows,
                    ncols=ncols, **l._offset(playcards))
        dx = ix if align == LEFT else l.size.width - l.XS
        dy = (len(bases) / f_cols + int(len(bases) % f_cols > 0)) * l.HYS
        l._set_horz_talon(x=dx, y=l.YM + dy, texts=texts, align=align,
                          anchor='s', wastecards=0)
        anchor = 'w' if align== RIGHT else'e'
        s['talon'].set_rounds(anchor='sss' if l._has_waste else anchor)


class MilliganLayout(Layout):
    """
    static block (top):
        - reserves. (_offset if holdcards. ncards if reserve_texts).
        - rows. (_offset if playcards).
        - foundations. (vert SUITS. max of 10 cols)

    dynamic block (bottom):  align LEFT or RIGHT side. default RIGHT.
        - talon (corner). waste (if Game._waste_. wastecards ignored).

    :cvar func _offset: dict('ycards': value)
    """
    _offset = lambda self, cards: {'ycards': cards}

    def create(self, rows, ncols=None, playcards=16, bases=None,
               reserves=0, reserve_texts=False, holdcards=0,
               align=LEFT, texts=False, **kw):
        l, s = self, self.s
        ncols = ncols or rows
        bases = bases if type(bases) in (list, tuple) else l.SUITS
        f_rows = min(5, l.NSUIT if bases is l.SUITS else len(bases))
        t_size = 1 + int(l._has_waste)
        max_rows = max(rows, reserves + t_size)
        center = lambda p: (max_rows - p) * l.HXS / 2
        ix = l.size.width + l.XM
        sx = 0 if align == LEFT else l.XM + bases / f_rows * l.XS
        sx += 0 if align == RIGHT else l.XM + t_size * l.XS
        sx += center(t_size + reserves)
        l._set_horz_reserves(x=ix + sx, y=l.YM, reserves=reserves,
                             reserve_texts=reserve_texts, anchor='s',
                             **l._offset(holdcards))
        dy = l.TEXT_HEIGHT if texts else 0
        l._set_rows(x=ix + center(rows), y=l.size.height + dy, rows=rows,
                    ncols=ncols, **l._offset(playcards))
        dy = l.TEXT_HEIGHT if reserves and reserve_texts else 0
        dx = 0 if align == LEFT else l.size.width - l.XS
        l._set_horz_talon(x=ix + dx, y=l.YM, texts=texts, align=align,
                          anchor='s', wastecards=0)
        anchor = 'w' if align== RIGHT else'e'
        s['talon'].set_rounds(anchor='ss' if l._has_waste else anchor)
        dx = l.size.width if align == LEFT else 0
        l._set_vert_foundations(x=l.XM + dx, y=l.YM, nrows=f_rows,
                                bases=bases)